function Compile(el, vm) {
	this.$vm = vm;
	// el 可以是元素选择器，也可以是DOM元素
	this.$el = this.isElementNode(el) ? el : document.querySelector(el);

	// 模板解析主要流程
	if (this.$el) {
		// 1. 将el的所有子元素添加到文档碎片节点
		this.$fragment = this.node2Fragment(this.$el);
		// 2. 解析文档碎片节点中模板语法
		this.init();
		// 3. 将解析好文档碎片节点添加到el，生效
		this.$el.appendChild(this.$fragment);
	}
}

Compile.prototype = {
	node2Fragment: function (el) {
		var fragment = document.createDocumentFragment(),
			child;

		// 将el第一个子元素添加到文档碎片中，一旦元素添加进行，就会从DOM树移除
		// 第二次，将el第一个子元素（实际是第二个子元素）添加到文档碎片中
		// 直到将el所有子元素添加到文档碎片中，结束
		while ((child = el.firstChild)) {
			fragment.appendChild(child);
		}

		return fragment;
	},

	init: function () {
		this.compileElement(this.$fragment);
	},

	compileElement: function (el) {
		// 提取所有子节点
		var childNodes = el.childNodes,
			me = this;

		// 转换成真数组进行遍历
		[].slice.call(childNodes).forEach(function (node) {
			// 提取当前子节点的文本内容
			var text = node.textContent;
			// 定义正则：用来匹配插值语法
			var reg = /\{\{(.*)\}\}/;

			// 判断当前子节点是否是元素节点
			if (me.isElementNode(node)) {
				// 解析子节点的指令属性
				me.compile(node);

				// 判断当前子节点是否是文本节点并且文本内容是否包含插值语法
			} else if (me.isTextNode(node) && reg.test(text)) {
				// 解析子节点文本内容的插值语法
				// node 当前子节点
				// RegExp.$1 插值语法中的表达式
				me.compileText(node, RegExp.$1);
			}

			// 判断当前子节点是否还有子节点
			if (node.childNodes && node.childNodes.length) {
				// 如果有，对所有子节点进行递归编译
				me.compileElement(node);
			}
		});
	},

	compile: function (node) {
		// 提取当前子节点所有属性节点
		var nodeAttrs = node.attributes,
			me = this;

		// 转换成真数组遍历
		[].slice.call(nodeAttrs).forEach(function (attr) {
			// 提取属性名
			var attrName = attr.name;
			// 判断当前属性名是否是指令属性 v-
			if (me.isDirective(attrName)) {
				// 提取属性值
				var exp = attr.value;
				// 对属性名截取，去掉 v-
				var dir = attrName.substring(2);
				// 事件指令
				if (me.isEventDirective(dir)) {
					// 给当前子节点绑定事件和回调函数
					compileUtil.eventHandler(node, me.$vm, exp, dir);
					// 普通指令
				} else {
					compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
				}

				// 删除掉解析完的指令属性
				node.removeAttribute(attrName);
			}
		});
	},

	compileText: function (node, exp) {
		compileUtil.text(node, this.$vm, exp);
	},

	isDirective: function (attr) {
		return attr.indexOf("v-") == 0;
	},

	isEventDirective: function (dir) {
		return dir.indexOf("on") === 0;
	},

	isElementNode: function (node) {
		return node.nodeType == 1;
	},

	isTextNode: function (node) {
		return node.nodeType == 3;
	},
};

// 指令处理集合
var compileUtil = {
	text: function (node, vm, exp) {
		this.bind(node, vm, exp, "text");
	},

	html: function (node, vm, exp) {
		this.bind(node, vm, exp, "html");
	},

	model: function (node, vm, exp) {
		this.bind(node, vm, exp, "model");

		var me = this,
			val = this._getVMVal(vm, exp);
		node.addEventListener("input", function (e) {
			var newValue = e.target.value;
			if (val === newValue) {
				return;
			}

			me._setVMVal(vm, exp, newValue);
			val = newValue;
		});
	},

	class: function (node, vm, exp) {
		this.bind(node, vm, exp, "class");
	},

	// node 当前子节点
	// vm MVVM实例
	// exp 表达式
	// dir 指令
	// {{}} v-text v-html v-class
	bind: function (node, vm, exp, dir) {
		// 取出一个更新DOM的方法
		var updaterFn = updater[dir + "Updater"];

		// 先获取表达式的值，再调用更新DOM的方法
		updaterFn && updaterFn(node, this._getVMVal(vm, exp));

		// 除了事件指令以外的其他所有模板语法，都会new Watcher
		// 插值语法
		// 其他指令语法
		new Watcher(vm, exp, function (value, oldValue) {
			// 更新DOM的方法
			updaterFn && updaterFn(node, value, oldValue);
		});
	},

	// 事件处理
	eventHandler: function (node, vm, exp, dir) {
		// 提取事件名称
		var eventType = dir.split(":")[1],
			// 提取事件回调函数
			fn = vm.$options.methods && vm.$options.methods[exp];

		if (eventType && fn) {
			// 绑定事件
			// 改变回调函数的this指向，为vm
			node.addEventListener(eventType, fn.bind(vm), false);
		}
	},

	// 获取表达式的值
	_getVMVal: function (vm, exp) {
		var val = vm._data;
		exp = exp.split(".");
		exp.forEach(function (k) {
			val = val[k];
		});
		return val;
	},

	_setVMVal: function (vm, exp, value) {
		var val = vm._data;
		exp = exp.split(".");
		exp.forEach(function (k, i) {
			// 非最后一个key，更新val的值
			if (i < exp.length - 1) {
				val = val[k];
			} else {
				val[k] = value;
			}
		});
	},
};

// 操作DOM的集合
var updater = {
	textUpdater: function (node, value) {
		node.textContent = typeof value == "undefined" ? "" : value;
	},

	htmlUpdater: function (node, value) {
		node.innerHTML = typeof value == "undefined" ? "" : value;
	},

	classUpdater: function (node, value, oldValue) {
		var className = node.className;
		className = className.replace(oldValue, "").replace(/\s$/, "");

		var space = className && String(value) ? " " : "";

		node.className = className + space + value;
	},

	modelUpdater: function (node, value, oldValue) {
		node.value = typeof value == "undefined" ? "" : value;
	},
};
